(function ( exports ) {

    //region privates

    var _separator = '.';

    function _splitSelector ( selector ) {
        var res = null;
        if ( typeof selector != 'undefined' && selector != null ) {
            res = (selector.split(_separator)).map(function ( item ) {
                return item.trim();
            });
        }

        return res.reverse();
    }

    function _findNodes( node, selectors ) {
        var currentSelector = selectors.pop(),
            matchItems = [];

        if ( selectors.length === 0 ) {
            matchItems = _getNodes(node, currentSelector);
        }
        else {
            matchItems = matchItems.concat(_findNodes(_getFirstMatch(node, currentSelector), selectors));
        }

        return matchItems;
    }

    function _getNodes ( node, selector ) {
        var rtrn = [];
        for ( var i = 0, len = node.childNodes.length; i < len; i++ ) {
            if ( node.childNodes[i].localName == selector ) {
                rtrn.push(new ExplorableNode(node.childNodes[i]));
            }
        }
        return rtrn;
    }

    function _processChildNodes ( root, processor ) {
        for ( var i = 0, len = root.childNodes.length; i < len; i++ ) {
            processor.call(this, root.childNodes[i]);
        }
    }

    function _findNode ( node, selectors ) {
        var currSlctr = selectors.pop(),
            item, tmp;

        if ( selectors.length === 0 ) {
            tmp = _getFirstMatch(node, currSlctr);

            if ( typeof tmp != 'undefined' ) {
                item = new ExplorableNode(tmp);
            }
        }
        else {
            tmp = _getFirstMatch(node, currSlctr);

            if ( typeof tmp != 'undefined' ) {
                item = _findNode(tmp, selectors);
            }
            else {
                throw new Error(COSA.Util.formatError('ENODE_CANTFINDNODE', currSlctr));
            }
        }

        return item;
    }

    function _getFirstMatch ( parent, localName ) {
        var res;
        for ( var i = 0, len = parent.childNodes.length; i < len; i++ ) {
            if ( parent.childNodes[i].localName == localName ) {
                res = parent.childNodes[i];
                break;
            }
        }

        return res;
    }

    function _getKeyValue ( node ) {
        var key = _getFirstMatch(node, 'key'),
            value = _getFirstMatch(node, 'value');

        return { key: key.textContent, value: value };
    }

    var _findNodes2 = function (node, level, selectorArr) {
        var matchItems = [];
        for (var i = 0, len = node.childNodes.length; i < len; i++) {
            if (node.childNodes[i].tagName.toLowerCase() == selectorArr[level]) {
                matchItems.push(node.childNodes[i]);
            }
        }
        var result = [];
        if (level < selectorArr.length - 1) {
            for (i = 0, len = matchItems.length; i < len; i++) {
                Array.prototype.push.apply(result, _findNodes(matchItems[i], ++level, selectorArr));
            }
        }
        else {
            for (i = 0, len = matchItems.length; i < len; i++) {
                result.push(new ExplorableNode(matchItems[i]));
            }
            return result;
        }
        return result;
    };

    //endregion privates

    //region ENode

    function ExplorableNode ( root ) {
        this.root = root;
    }

    ExplorableNode.prototype = {
        node: function ( slctr ) {
            return _findNode(this.root, _splitSelector(slctr));
        },
        nodes: function ( slctr ) {
            var splitSlctr, res;
            if ( typeof slctr != 'undefined' ) {
                splitSlctr = _splitSelector(slctr);
                if ( splitSlctr.length === 1 ) {
                    res = _getNodes(this.root, splitSlctr[0]);

                }
                else {
                    res = _findNodes(this.root, splitSlctr);
                }
            }
            else {
                res = [];
                _processChildNodes(this.root, function ( child ) {
                    res.push(new ExplorableNode(child));
                });
            }

            return res;
        },
        namedNodes: function () {
            var rs;

            if ( this.root.childNodes.length > 0 ) {
                rs = {};
                _processChildNodes(this.root, function ( child ) {
                    rs[child.localName] = new ExplorableNode(child);
                });
            }
            return rs;
        },
        getDouble: function () {
            return parseFloat(this.root.textContent);
        },
        attr: function ( attrName ) {
            var namedItem, res;

            if ( typeof attrName != 'undefined' ) {
                namedItem = this.root.attributes.getNamedItem(attrName);
                if ( namedItem !== null ) {
                    res = namedItem.value;
                }
            }

            return res;
        },
        keyValueCollection: function () {
            var rs, tmp;

            if ( this.root.childNodes.length > 0 ) {
                rs = {};
                for ( var i = 0, len = this.root.childNodes.length; i < len; i++ ) {
                    tmp = _getKeyValue(this.root.childNodes[i]);
                    rs[tmp.key] = new ExplorableNode(tmp.value);
                }
            }

            return rs;
        },
        text: function () {
            return this.root.textContent;
        },
        toInt: function () {
            return parseInt(this.root.textContent);
        },
        toDouble: function () {
            return parseFloat(this.root.textContent);
        },
        toBoolean: function () {
            return this.root.textContent == 'true';
        },
        toEntityReference: function () {
            var n = this.namedNodes();
            return new COSA.EntityReference(n.LogicalName.text(), n.Id.text());
        },
        toDateTime: function () {
            return new Date(this.root.textContent);
        }
    };

    //endregion ENode

    //region TypedNodeMapper

    function TypedNodeMapper ( context ) {
        this.context = context;
        this.map = null;
    }

    TypedNodeMapper.prototype = {
        applyValues: function ( values ) {
            var converter, prts;

            if ( typeof values == 'undefined' || values === null ) {
                this.context = null;
            }
            else {
                converter = new StringConverter();
                for ( var prop in this.map ) {
                    prts = this.map[prop].split(':');
                    if ( values.hasOwnProperty(prts[1])) {
                        this.context[prop] =
                            converter[prts[0]]
                                .call(this, values[prts[1]]);
                    }
                }
            }
        },
        appVal: function ( values ) {
            var cnvrtr = new StringConverter(), s;
            for ( var prp in this.map ) {
                this.map[prp].forEach(function ( nodeName ) {
                    this.context[nodeName] = (cnvrtr[prp].bind(values[nodeName]))();
                });
            }
        },
        useMap: function ( map ) {
            this.map = map;
            return this;
        }
    };

    //endregion TypedNodeMapper

    function intCnvrtr () {
        return parseInt(this.text());
    }

    //region Parser

    function StringConverter () {
    }

    StringConverter.prototype = {
        string: function ( node ) {
            return node.text();
        },
        int: function ( node ) {
            return parseInt(node.text());
        },
        boolean: function ( node ) {
            return node.text() == 'true';
        },
        double: function ( node ) {
            return parseFloat(node.text());
        },
        localizedLabelSet: function ( node ) {
            return (new COSA.LocalizedLabelSet()).deserialize(node);
        },
        localizedLabelCollection: function ( node ) {
            return (new COSA.LocalizedLabelCollection()).deserialize(node);
        },
        localizedLabel: function ( node ) {
            return (new COSA.LocalizedLabel()).deserialize(node);
        },
        managedProperty: function ( node ) {
            return (new COSA.ManagedProperty()).deserialize(node);
        },
        valueNode: function ( node ) {
            return (new COSA.ValueNode()).deserialize(node);
        },
        stringCollection: function ( node ) {
            return (new COSA.StringCollection()).deserialize(node);
        },
        boolOptionSetMetadata: function ( node ) {
            return (new COSA.BoolOptionSetMetadata()).deserialize(node);
        },
        optionSetMetadata: function ( node ) {
            return (new COSA.OptionSetMetadata()).deserialize(node);
        },
        optionMetadata: function ( node ) {
            return (new COSA.OptionMetadata()).deserialize(node);
        },
        optionsCollection: function ( node ) {
            return (new COSA.OptionsCollection()).deserialize(node);
        },
        attributeMetadataCollection: function ( node ) {
            return (new COSA.AttributeMetadataCollection()).deserialize(node);
        },
        privilegesMetadataCollection: function ( node ) {
            return (new COSA.PrivilegesMetadataCollection()).deserialize(node);
        },
        mMRelationshipCollection: function ( node ) {
            return (new COSA.ManyToManyRelationshipCollection()).deserialize(node);
        },
        oMRelationshipCollection: function ( node ) {
            return (new COSA.OneToManyRelationshipCollection()).deserialize(node);
        },
        mORelationshipCollection: function ( node ) {
            return (new COSA.ManyToOneRelationshipCollection()).deserialize(node);
        },
        menuConfiguration: function ( node ) {
            return (new COSA.MenuConfiguration()).deserialize(node);
        },
        cascadeConfiguration: function ( node ) {
            return (new COSA.CascadeConfiguration()).deserialize(node);
        }
    };

    //endregion Parser

    exports.ExplorableNode = ExplorableNode;
    exports.TypedNodeMapper = TypedNodeMapper;

})(COSA.Xml);